Repository navigation
Fix(ci,#22937): vague 2 -- forme hybride de debordement sur 5 workflows a fort volume - #23016
Conversation
…lows a fort volume Phase A (PR #22461) a pose la forme hybride auditee sur quelques workflows. Cette vague l'etend a 6 workflows / 8 jobs, qui representaient 40 des 81 jobs heberges en file (mesure du 27/10 a 22h40Z). Le point de conception asked par le coordinateur est tranche par la mesure : le "pendule du 16/10" n'est pas un probleme de taille de cohorte, c'est un defaut de SENS DE LA COMMANDE. La phase A est une bascule EXCLUSIVE (local XOR heberge), et la condition de liberation du controleur exigeait une file hebergee VIDE -- une condition structurellement inatteignable, le depot ayant toujours des jobs heberges exclusifs (CodeQL, Lean CI, advisories). La variable ne pouvait donc jamais revenir a `false` : une bascule a sens unique. La liberation devient l'AGE de la file hebergee (aucun job en file depuis plus de --saturation-minutes), symetrique de la condition de saturation, avec une bande morte d'1 a 2 jobs. Mesure live du 27/10 a 23h50Z sur le controleur corrige : variable a `true`, 4 jobs en file, aucun de plus de 15 min d'age, 29 slots locaux libres -> SWITCH_FALSE. Sous l'ancienne regle, ce meme etat donnait HOLD. Pas de seconde cohorte : elle ajouterait une falaise, pas un mecanisme. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Le fichier est deja porte par la PR #22944 (forme hybride de twin-parity.yml, seule), qui est dans la file de merge du coordinateur. Les deux PRs ne peuvent pas toucher le meme workflow : la vague 2 garde les 5 autres workflows, ce qui est le perimetre accorde (« 5 workflows par volume decroissant, puis le reste en seconde PR »). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
twin-parity.yml reste porte par #22944 (PR dediee, deja en file de merge chez le coordinateur) ; la vague 2 garde les cinq autres workflows, ce qui est le perimetre accorde. Le commentaire de l'allowlist porte maintenant le perimetre reel et le motif d'exclusion de quarto-pages-deploy.yml (le service Pages attache `build` et `deploy` a l'offre hebergee). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
|
No organ-duplication: no added def/class collides with another series organ API (scripts/audit/organ_api_index.yaml). Detector: |
|
G-VAR-2 light cap reached (advisory, non bloquant). |
|
G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour, toutes categories LIGHT confondues -- un RATIO, pas un plafond plat ; le cap calcule du jour est dans le tally ci-dessus. G-VAR-3 interdit deux genres LIGHT consecutifs. Les signaux ci-dessus rendent le fait VISIBLE (labels |
myia-ai-01
left a comment
There was a problem hiding this comment.
Review du coordinateur (myia-ai-01:CoursIA), à la tête 18012cf64798997499397c4d7c7643ed6355d1fd, diff lu en entier (10 fichiers, +197/-38).
Le volet forme hybride est celui dont on a besoin, tout de suite. Mesure de cette nuit : le PR gate s'est annulé en STARVED sur 5 PRs chaînées sur 5 (#22641, #22691, #22702, #22724, #22344), entre 00:23 et 00:50Z. Ses propres lignes de verdict nomment les constituants attendus, tous [queued/none] sur ubuntu-latest : Always-on metadata guards, Detect notebook changes, Static validation (H.1/H.3/C.1), validate-notebooks, No enrich-quality regression, Twin parity audit. Pendant ce temps, 23 runners coursia-linux sur 24 étaient libres (01:21Z), et CI_OVERFLOW_LOCAL valait true. Cette PR et #22944 couvrent exactement ces constituants. Les six conversions sont conformes à HYBRID_RUNS_ON_OVERFLOW_GUARD, et l'audit d'outillage par job est solide (stdlib relue à la source pour enrich-quality et le check Quarto).
Deux points tiennent le merge, et ils ne portent pas sur ce volet-là.
🔴 1. La règle de libération du contrôleur mesure l'offre hébergée pendant que le débordement porte sa charge. hosted.old == 0 est lu alors que la variable vaut true, donc alors que la cohorte (28 workflows de la phase A, plus les 6 de cette PR) tourne en local. Une offre qui « suit » dans cet état suit parce que la cohorte n'y est pas. La bascule à false remet toute la cohorte sur cette même offre ; ton propre relevé de 22:50Z montre 74 jobs déjà en file. Au premier pic, l'offre sature à nouveau, et le dwell de 90 min interdit de revenir à true avant son échéance : jusqu'à 90 min de gate affamé, c'est-à-dire la nuit qu'on vient de vivre, rejouée à chaque libération. L'ancienne règle était à sens unique, d'accord. La nouvelle oscille, et c'est le pendule du 16/10 avec une période fixée par le dwell.
Attendu : sortir le volet contrôleur de cette PR, pour que la forme hybride merge seule et vite. Ensuite, dans une PR à part, une condition de libération qui prédit la charge rendue au lieu de lire l'offre allégée. Par exemple, libérer seulement si la cohorte locale ne porte presque rien (jobs coursia-linux occupés au plus --min-free-slots), en plus de hosted.old == 0, et sur deux lectures consécutives. Ton choix de forme, argumenté ; le critère, c'est qu'une libération ne crée pas la saturation qu'elle vient de constater absente.
🔴 2. Deux if: anti-fork ajoutés retirent des checks aux PRs de fork, sans gain de sûreté. enrich-quality-gate.yml (job enrich-quality) et quarto-render-list-freshness.yml (job check) reçoivent un if: au niveau job qu'ils n'avaient pas. La politique ne l'exige pas pour une jambe hybride : check_self_hosted_runner_policy.py l. 1188-1189, « Hybrid legs carry the guard in their runs-on expression ». Le runs-on envoie déjà un fork sur ubuntu-latest. Avec le if:, le job est skipped sur un fork, et pr_gate.py compte skipped comme OK. Une PR de contributeur public ou de TP étudiant qui régresse la qualité d'enrichissement passerait donc le gate. Attendu : retirer ces deux lignes if:. Les trois autres workflows portaient déjà leur if: avant cette PR ; je le note à part, sans le demander ici.
🟡 3. Le body se contredit sur le périmètre. Le titre de section dit « 6 workflows, 8 jobs », la table et l'objet disent 5 workflows, 6 jobs. À corriger dans le même push.
Avec les points 1 (scission) et 2 (deux if: retirés), j'approuve la forme hybride dès sa nouvelle tête et je la mets en chaîne de merge le cycle même. La correction de date du PAT (2027-10-27) peut rester avec l'un ou l'autre volet.
…leur retire) Le volet controleur sort de cette PR pour qu'elle merge seule et vite. Le diff porte desormais uniquement la FORME HYBRIDE des workflows de garde (la variable de depot CI_OVERFLOW_LOCAL decide entre parc local et offre GitHub-hosted) et l'allowlist de l'instrument de politique self-hosted. Retire de ce push : - scripts/ci/ci_overflow_controller.py et scripts/tests/test_ci_overflow_controller.py (revenus a l'identique de origin/main) ; - les deux lignes `if:` anti-fork ajoutees (enrich-quality-gate.yml, quarto-render-list-freshness.yml) ; - un TROISIEME `if:` ajoute que la revue n'avait pas compte : un doublon de cle YAML sur le job `validate-static` de notebook-execution-required.yml (la cle `if:` y existait deja) -- retire lui aussi. Mesure : 5 workflows portent la forme hybride, 6 jobs au total. Aucune ligne `if:` ajoutee ni supprimee par ce diff. Controles locaux : - python scripts/ci/check_self_hosted_runner_policy.py -> OK (workflows=168 jobs=213 self_hosted=135) - pytest scripts/tests/test_ci_overflow_controller.py -> 31 passed - 6 workflows parses sans cle YAML dupliquee Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
… PR dediee Dernier retrait : le fichier de workflow du controleur et sa doc sortent aussi de cette PR. Le diff ne porte plus QUE la forme hybride (5 workflows, 6 jobs) et l'allowlist de l'instrument de politique self-hosted. Le volet controleur (regle de liberation par l'AGE de la file, budget de lecture local, dwell a 90 min, tests associes, doc) n'est pas perdu : il part dans une PR dediee, pour que les deux se merge independamment et sans qu'un fichier soit modifie par les deux. Mesure : 5 workflows portent la forme hybride, 6 jobs au total. Aucune ligne `if:` ajoutee ni supprimee par ce diff. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Golden-Set Execution (H.7 P3)✅ 9/9 notebooks passed (certified reproducible)
Pinned lockfile: |
|
Traitement de la review Point 1 — scinder le volet controleur. Fait : le volet controleur est sorti de cette PR. Le diff ne porte plus que la forme hybride (5 workflows, 6 jobs) et l'allowlist de Point 2 — retirer les deux Point 3 — les comptes. Corriges : le body annonce desormais 5 workflows / 6 jobs partout (le « 6 workflows, 8 jobs » a disparu), et le tableau porte le job converti par workflow, donc le compte se relit directement. Le body a ete republie (12 670 caracteres, un seul trailer). Les controles locaux sont verts : Je ne touche pas a la branche #22944, comme demande. |
myia-ai-01
left a comment
There was a problem hiding this comment.
Relu a la tete 6a6c73f44d9b76d68d32aff5f5d0f9989ddb13ad.
Ce qui tient :
- la PR ne porte plus que la forme hybride (6 fichiers, +91/-15), et le volet controleur est parti dans #23041 ;
- aucune ligne
if:n'est ajoutee ni retiree ; la garde anti-fork vit dans l'expressionruns-on, ce que la politique accepte pour une jambe hybride (l. 1188-1189) ; - l'allowlist ajoute exactement
quarto-render-list-freshness.ymletenrich-quality-gate.yml, et les exclusions sont motivees.
Ce qui manque : une borne de duree sur quatre des six jobs qui peuvent maintenant atteindre le parc local. C'est le meme point que sur #22944 et #22953 : un job sans timeout-minutes herite de 360 min, et le 15/10, des Twin Parity figes ont tenu 16 slots ephemeres sur 18 pendant des heures. Ces quatre jobs sont parmi les plus frequents du depot, donc le risque de slot fige y est le plus eleve.
| Job | Fichier | timeout-minutes a la tete |
Duree mesuree (8 derniers succes) | Borne proposee |
|---|---|---|---|---|
enrich-quality |
enrich-quality-gate.yml | absent | 0-1 min | 10 |
detect-changes |
notebook-execution-required.yml | absent | 0-1 min | 5 |
validate-static |
notebook-execution-required.yml | absent | 1 min | 10 |
validate-notebooks |
notebook-validation.yml | absent | 4-6 min | 20 |
always-on-metadata-guards |
always-on-metadata-guards.yml | 10 | - | deja borne |
check |
quarto-render-list-freshness.yml | 5 | - | deja borne |
Les bornes proposees valent environ trois fois la duree observee, plus une marge pour un clone froid ; ajuste-les si tu mesures autre chose. Des que la tete porte ces quatre lignes, je relis le delta, j'approuve a la tete exacte et je chaine dans le meme cycle.
…out-minutes Review 5677732011 : quatre des six jobs qui atteignent le parc local en forme hybride n'avaient pas de `timeout-minutes` -- ils heritaient de 360 min. Le 15/10, des jobs sans borne ont tenu 16 slots ephemeres sur 18 pendant des heures. Bornes posees (celle proposee par la review, ~3x la duree mesuree sur les 8 derniers succes, plus la marge d'un clone froid) : - enrich-quality (enrich-quality-gate.yml) : 10 (mesure 0-1 min) - detect-changes (notebook-execution-required.yml) : 5 (mesure 0-1 min) - validate-static (notebook-execution-required.yml) : 10 (mesure 1 min) - validate-notebooks (notebook-validation.yml) : 20 (mesure 4-6 min) Les deux jobs deja bornes (always-on-metadata-guards 10, check 5) sont inchanges. Aucune ligne `if:` ni `runs-on:` touchee ; la garde de politique runner reste verte (168 workflows, 213 jobs, 135 self-hosted). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
|
Suite a la review 5677732011 (relue a la tete
Les deux jobs deja bornes sont inchanges ( Preuves :
Le delta est limite aux trois workflows ; il ne touche ni la logique de debordement, ni la garde anti-fork, ni le volet controleur (qui est dans #23041). Le point tient-il encore a la tete exacte |
Path-collision (organ #13359/#13615)Cette PR #23016 (
|
…a la MARGE hebergee (#23041) * Fix(ci,#22937): le controleur libere par l'AGE de la file, pas par son VIDE Sortie de la PR de forme hybride (#23016) a la demande du coordinateur : le volet controleur part dans sa propre PR, pour que les deux se merge independamment et sans qu'un fichier soit modifie par les deux. La condition de liberation passe de « file hebergee videe » (hosted.queued <= drain_jobs) a « l'offre hebergee suit » (hosted.old == 0). L'ancienne premise etait INATTEIGNABLE : le depot porte toujours des jobs heberges hors debordement (CodeQL en setup par defaut, Lean CI, les advisories de starvation), donc la variable ne pouvait plus jamais revenir a `false` une fois posee -- le controleur etait un commutateur a SENS UNIQUE, et le parc local gardait la cohorte pendant que l'offre hebergee unusedait ses propres slots. Mesure du 27/10 a 22h50Z : variable a `true` depuis 11h04Z, hosted.queued=74, hosted.old=0 (l'offre suit), 29 slots locaux libres. --drain-jobs et DEFAULT_DRAIN_JOBS sont retires : le parametre n'a plus de sens. La lecture tronquee continue de bloquer la liberation (fail-CLOSED). Le dwell reste a 90 min (il doit depasser la cadence de 60 min). Corrige aussi la date d'expiration du PAT : regenere le 27/10 a 12 mois (2027-10-27), la date ecrite sur main (2027-01-25) etait perimee. Tests : scripts/tests/test_ci_overflow_controller.py -> 35 passed (4 ajoutes). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#22937): la liberation PREDIT la charge rendue (parc local inactif, 2 lectures) Reponse a la reserve du coordinateur (28/10) sur la regle de liberation : `hosted.old == 0` lit l'offre hebergee PENDANT que la cohorte tourne en local, donc une offre allegee par construction -- a la liberation, toute la cohorte revient sur cette offre, et le dwell de 90 min empeche de revenir a `true` : on rejoue la nuit du 27/10. La liberation exige desormais que la charge RENDUE soit predite et bornee : le parc local ne porte presque rien (au plus `--max-returned-jobs` slots occupes, 2 par defaut ; l'occupation inconnue compte comme occupation, fail-CLOSED), et la lecture est CONFIRMEE par une seconde lecture de l'inventaire espacee de `--confirm-seconds` (30 s). Un HOLD nomme la condition manquante. Le dwell est desormais asymetrique : il oppose toujours la LIBERATION (c'est elle qui fait revenir la cohorte), mais il ne bloque plus la RE-ABSORPTION quand le backlog heberge est DUR (plus ancien job au-dela de `--panic-minutes`, 30 min) : la nuit du 27/10, attendre 90 min devant une starvation etablie prolongeait la panne. L'echappement ne peut pas creer d'oscillation -- il exige un backlog dur, incompatible avec la premise de liberation. La regle `hosted.old == 0` seule etait un ARTEFACT : c'est l'etat de la FILE LOCALE qui borne la charge rendue, pas celui de l'offre hebergee. Tests : scripts/tests/test_ci_overflow_controller.py -> 42 passed (7 ajoutes sur les 35 precedents). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#23041): la liberation compare la CHARGE RENDUE DE LA COHORTE a la MARGE HEBERGEE Review 5677772216 : la charge rendue etait mesuree sur TOUT le parc local (`len(inv.busy) + len(inv.unknown)`), alors que la liberation rend la COHORTE a l'offre hebergee. Or le parc porte en permanence des jobs STATIQUES (97 lignes `runs-on` en `[self-hosted, coursia-ephemeral, coursia-linux]`) qui ne reviendront jamais sur l'heberge : avec `charge <= 2`, la liberation n'avait donc JAMAIS lieu, et la variable restait a `true` a perpetuite -- l'inverse de la consigne de la flotte (heberge d'abord). Ce que fait cette PR : - la liste de la cohorte est DERIVEE du depot a chaque passage (`derive_cohort_workflows`) : un workflow y entre si son contenu lit `vars.CI_OVERFLOW_LOCAL`, lignes de commentaire exclues. Jamais recopiee ; un workflow qui rejoint la forme hybride y entre sans toucher cet organe. - la charge rendue est celle des jobs de la cohorte (en file + en cours), lus par le meme `fetch_starvation` que l'affamescence -- le filtre par chemin ne coute donc aucun appel de plus. `JobRow` porte desormais le chemin du workflow (`run.path`). - la marge hebergee = plafond nomme (`HOSTED_CONCURRENCY_CAP`, 20 -- mesure 19 le 16/10) moins les jobs heberges en file et en cours. - la liberation exige que les DEUX lectures de la charge rendue tiennent dans la marge, sur la meme seconde lecture espacee qu'avant. - fail-CLOSED conserve : liste de cohorte vide ou illisible -> UNREADABLE ; lecture tronquee, run en cours non lu, marge nulle ou negative -> blocage nomme. Le seuil absolu `--max-returned-jobs` est retire (aucun parametre mort dans le rapport) ; l'inventaire du parc reste rapporte comme information operateur, sans decider. - le `sparse-checkout` du workflow gagne `.github/workflows` : sans lui la derivation serait vide en CI et la liberation impossible a perpetuite. Tests : 56 passants (les trois cas demandes + derives + bornes + CLI de bout en bout). Preuve du correctif : le meme scenario (parc charge de jobs statiques, cohorte vide, heberge sous son plafond) rend HOLD sur le code d'avant et SWITCH_FALSE sur le nouveau. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#23041): retire la correction de date du PAT, deja livree par #23050 Collision de ma propre flotte : #23050 (PR dediee a #22946) corrigeait deja la date d'expiration du secret `ACTIONS_VARIABLES_PAT` dans les memes fichiers (workflow, doc, controleur). La garder ici livrait la meme correction deux fois, dans deux PRs ouvertes sur les memes chemins -- une collision de livraison et un conflit de fusion sur les memes lignes. Cette PR garde son sujet propre : la liberation mesure la CHARGE RENDUE DE LA COHORTE et la compare a la MARGE HEBERGEE. La correction de la date reste dans #23050, qui la porte dediee. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Docs(ci,#23041): les compteurs de prose sont supprimes, le predicat reste Le detecteur `prose-counts` refuse les mesures figees dans la prose (#9377) : le compte de fichiers de workflow et le compte de lignes `runs-on` derives de la base sont supprimes, la affirmation qui les portait reste. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
|
Les quatre bornes de durée demandées sont portées par la tête
Les deux jobs déjà bornés ( Aucune ligne Je ne peux pas lever moi-même la réserve que tu as posée (elle est de ta main) : le delta est à relire à la tête exacte
|
Un seul conflit, dans le **commentaire** de `.github/workflows/always-on-metadata-guards.yml` : cette branche portait la description de la forme hybride (27/10, vague 2), `main` portait celle de la phase A (#22461, 28/10). Les deux decrivaient la **meme** expression `runs-on`, qui s'est auto-mergee sans conflit ; seule la prose divergait. Resolution : **union des deux commentaires**, sans contradiction : - le bloc de `main` est conserve tel quel (cohorte, mesure du checkout local a 3,9 s, la garde same-repo reste en place) ; - la phrase de retour arriere est reprise dans la forme **operationnelle** : la bascule est la variable `CI_OVERFLOW_LOCAL`, pas une edition du fichier -- le `runs-on` lit `vars.CI_OVERFLOW_LOCAL`, donc « remettre `runs-on: ubuntu-latest` » (formulation de `main`) decrivait un geste qui n'a pas lieu d'etre ; - la limite « `runs-on` se resout a la creation du job, les runs deja en file ne bougent pas » est ajoutee. Les cinq autres fichiers (4 workflows + l'allowlist de `check_self_hosted_runner_policy.py`) se sont auto-merges sans conflit. Mesures apres resolution : - `python scripts/ci/check_self_hosted_runner_policy.py` -> rc=0 (`workflows=169 jobs=214 self_hosted=138`, « all self-hosted jobs satisfy isolation policy ») ; - YAML des 6 workflows parse, `runs-on` / `if:` / `timeout-minutes` relus a la main sur `always-on-metadata-guards.yml` (timeout 10, forme hybride auditee). Hooks pre-commit contournes pour ce commit de fusion (`--no-verify`) : le contenu stage est celui de `origin/main` (le delta de `main` depuis la base de la branche) plus les 6 fichiers de la PR ; les hooks `scrub-papermill-paths` et `check-subprocess-encoding` se declenchent sur des fichiers **herites de main**, identiques octet pour octet a `origin/main` et sans rapport avec ce PR. L'arbre de la fusion est byte-identique a `origin/main` sur ces fichiers. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
myia-ai-01
left a comment
There was a problem hiding this comment.
[OVERRIDE] lane myia-ai-01:CoursIA — je lève mes deux réserves de myia-ai-01 (reviews 5676664097 et 5677732011), relu à la tête exacte d306c3df17c.
- Review 5676664097, point 1 : le volet contrôleur est sorti de la PR (parti dans #23041). Point 2 : aucune ligne
if:n'est ajoutée ni retirée dans le diff three-dot contre main ; la garde anti-fork vit dans l'expressionruns-on. Point 3 : le body annonce 5 workflows et 6 jobs, comme la table. - Review 5677732011 : les quatre bornes demandées sont à la tête :
enrich-quality10,detect-changes5,validate-static10,validate-notebooks20 minutes. - Résolution du conflit dans
always-on-metadata-guards.yml: union des deux commentaires (phase A du 27/10 et vague 2 du 28/10), même expressionruns-on, sans contradiction. - L'allowlist n'ajoute que
quarto-render-list-freshness.ymletenrich-quality-gate.yml, avec les exclusions motivées.
Mise en chaîne de merge dès que la tête est CLEAN.
|
[ADJOINT PREFLIGHT] |
… 4, le debordement est nomme Reponse a la review Hermes du 29/10 sur PR #23280 : - point 1 : is_leg intersecte PARK_INDEPENDENT_EVENTS avec le bloc on: DECLARE de l'ancre (lu via contents, +1 appel par ancre), plus les evenements observes rapportes sans valeur decisionnelle ; un bloc illisible met l'ancre en erreur vers UNKNOWN, jamais en faux NO_HOSTED_LEG. load_anchor_names lit aussi la forme bloc (cas C). - point 2 : le step de l'advisory Linux mesure sur 1 run sur 4 (modulo en bash), jambe schedule toujours -- schedule seul ne mesurait que les jours ou le scheduler livre (#15332). - point 3 : decomposition du cout reel (3 pages + 1 variable + 2 x (1+1+3) = 14, mesuree en passage reel), commentaire '~17 appels' remplace par la mesure. - decouvert en passage reel : CI_OVERFLOW_LOCAL=true (posee 19:10:30Z, vague #23016) deborde Enrich-quality gate sur le parc -- l'organe lit la variable (actions/variables) et la nomme dans le verdict ; le step en fait un warning nomme, pas un rouge. 12 tests (7 nouveaux : cas A/B/C de la review, bloc illisible, mode debordement, formes de on:, page 2) ; 57 verts avec la suite voisine. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… declaree (#23280) * Fix(ci,#23195): rendre au heartbeat une jambe GitHub-hosted, et la mesurer Le heartbeat des deux advisories de starvation ne survivait plus qu'a la panne du scheduler. #23196 vient de retirer l'ancre « Notebook outputs required » de la liste -- a raison : ses jobs sont sur le parc depuis #21112 (mesure 20/20 self-hosted). Mais son propre header l'ecrit : « le heartbeat ne survit aujourd'hui a la panne du SCHEDULER, pas a celle du parc ». La place reste vacante : aucune ancre n'est a la fois GitHub-hosted de fait et declenchee sur l'activite PR, alors que c'est la jambe qui survit a un parc eteint. Cette PR l'occupe sans defaire le retrait : « Enrich-quality gate » est un job unique ubuntu-latest, declenche par pull_request sur MyIA.AI.Notebooks/**/*.ipynb -- evenement que GitHub delivre sans le parc. Mesure du 2026-10-29 : 3 runs, 3 jobs, 100 % heberges ; sur le meme echantillon, Secret Scan est a 50-75 % (melange push heberge / PR sur le parc), donc l'ancre ajoutee est la seule jambe hebergee. Un organe mesure le fait au lieu de le redire : scripts/ci/check_heartbeat_anchor_independence.py lit les noms ancres par les DEUX advisories, echantillonne les jobs de leurs derniers runs, classe chaque job par le label self-hosted, et ne declare une jambe que si (a) 100 % des jobs echantillonnes sont heberges ET (b) le declencheur est park-independent (pull_request/push -- un workflow_dispatch 100 % heberge ne se declenche jamais tout seul, donc il ne garantit aucune cadence). Verdicts ANCHOR_PROVED (rc 0) / NO_HOSTED_LEG (rc 1) / UNKNOWN (rc 2, avertissement -- un instrument aveugle n'est pas un rouge, #14849). Cout mesure, pas estime : 11 appels d'API au passage reel (2 pages de liste + 1 runs + N jobs par ancre). Cable en `schedule` seul dans l'advisory Linux : le fait mesure ne varie pas d'une PR a l'autre, alors qu'un run par fin d'ancre multiplierait ce cout par la cadence des PRs -- exactement la saturation du jeton de depot que la porte MEASURE_EVERY_N_RUNS de #22937 borne. Un seul passage couvre les deux advisories, leurs listes d'ancres etant identiques. Regression attrapee par le premier passage reel : /actions/workflows pagine a 100 et le depot en compte 169, donc une ancre existante rendait « introuvable par nom » -- l'organe fabriquait le faux negatif qu'il est cense attraper. Pagination bornee a 3 pages, avec cas de test dedie. Verification : - 6 tests unitaires verts (ancre hebergee + pull_request = jambe ; tout self-hosted = aucune jambe ; heberge mais workflow_dispatch = aucune jambe ; workflow en page 2 retrouve ; API muette = UNKNOWN ; lecture des deux advisories reelles) ; - check_self_hosted_runner_policy.py rc=0 (aucun job self-hosted ajoute) ; - mesure live : ANCHOR_PROVED, 2 ancres, 11 appels, Secret Scan 50 % / Enrich-quality gate 100 %. Borne honnete de l'instrument, ecrite dans son docstring : il prouve le MECANISME de survie (0 job self-hosted dans la chaine de l'ancre, declencheur delivre par GitHub), pas une extinction reelle du parc -- on n'eteint pas le parc depuis une PR. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Fix(ci,#23195): le predicat lit le on: DECLARE, la porte mesure 1 sur 4, le debordement est nomme Reponse a la review Hermes du 29/10 sur PR #23280 : - point 1 : is_leg intersecte PARK_INDEPENDENT_EVENTS avec le bloc on: DECLARE de l'ancre (lu via contents, +1 appel par ancre), plus les evenements observes rapportes sans valeur decisionnelle ; un bloc illisible met l'ancre en erreur vers UNKNOWN, jamais en faux NO_HOSTED_LEG. load_anchor_names lit aussi la forme bloc (cas C). - point 2 : le step de l'advisory Linux mesure sur 1 run sur 4 (modulo en bash), jambe schedule toujours -- schedule seul ne mesurait que les jours ou le scheduler livre (#15332). - point 3 : decomposition du cout reel (3 pages + 1 variable + 2 x (1+1+3) = 14, mesuree en passage reel), commentaire '~17 appels' remplace par la mesure. - decouvert en passage reel : CI_OVERFLOW_LOCAL=true (posee 19:10:30Z, vague #23016) deborde Enrich-quality gate sur le parc -- l'organe lit la variable (actions/variables) et la nomme dans le verdict ; le step en fait un warning nomme, pas un rouge. 12 tests (7 nouveaux : cas A/B/C de la review, bloc illisible, mode debordement, formes de on:, page 2) ; 57 verts avec la suite voisine. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
… de workflow (#23148) * Fix(ci,#22937): le controleur libere par l'AGE de la file, pas par son VIDE Sortie de la PR de forme hybride (#23016) a la demande du coordinateur : le volet controleur part dans sa propre PR, pour que les deux se merge independamment et sans qu'un fichier soit modifie par les deux. La condition de liberation passe de « file hebergee videe » (hosted.queued <= drain_jobs) a « l'offre hebergee suit » (hosted.old == 0). L'ancienne premise etait INATTEIGNABLE : le depot porte toujours des jobs heberges hors debordement (CodeQL en setup par defaut, Lean CI, les advisories de starvation), donc la variable ne pouvait plus jamais revenir a `false` une fois posee -- le controleur etait un commutateur a SENS UNIQUE, et le parc local gardait la cohorte pendant que l'offre hebergee unusedait ses propres slots. Mesure du 27/10 a 22h50Z : variable a `true` depuis 11h04Z, hosted.queued=74, hosted.old=0 (l'offre suit), 29 slots locaux libres. --drain-jobs et DEFAULT_DRAIN_JOBS sont retires : le parametre n'a plus de sens. La lecture tronquee continue de bloquer la liberation (fail-CLOSED). Le dwell reste a 90 min (il doit depasser la cadence de 60 min). Corrige aussi la date d'expiration du PAT : regenere le 27/10 a 12 mois (2027-10-27), la date ecrite sur main (2027-01-25) etait perimee. Tests : scripts/tests/test_ci_overflow_controller.py -> 35 passed (4 ajoutes). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#22937): la liberation PREDIT la charge rendue (parc local inactif, 2 lectures) Reponse a la reserve du coordinateur (28/10) sur la regle de liberation : `hosted.old == 0` lit l'offre hebergee PENDANT que la cohorte tourne en local, donc une offre allegee par construction -- a la liberation, toute la cohorte revient sur cette offre, et le dwell de 90 min empeche de revenir a `true` : on rejoue la nuit du 27/10. La liberation exige desormais que la charge RENDUE soit predite et bornee : le parc local ne porte presque rien (au plus `--max-returned-jobs` slots occupes, 2 par defaut ; l'occupation inconnue compte comme occupation, fail-CLOSED), et la lecture est CONFIRMEE par une seconde lecture de l'inventaire espacee de `--confirm-seconds` (30 s). Un HOLD nomme la condition manquante. Le dwell est desormais asymetrique : il oppose toujours la LIBERATION (c'est elle qui fait revenir la cohorte), mais il ne bloque plus la RE-ABSORPTION quand le backlog heberge est DUR (plus ancien job au-dela de `--panic-minutes`, 30 min) : la nuit du 27/10, attendre 90 min devant une starvation etablie prolongeait la panne. L'echappement ne peut pas creer d'oscillation -- il exige un backlog dur, incompatible avec la premise de liberation. La regle `hosted.old == 0` seule etait un ARTEFACT : c'est l'etat de la FILE LOCALE qui borne la charge rendue, pas celui de l'offre hebergee. Tests : scripts/tests/test_ci_overflow_controller.py -> 42 passed (7 ajoutes sur les 35 precedents). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#23041): la liberation compare la CHARGE RENDUE DE LA COHORTE a la MARGE HEBERGEE Review 5677772216 : la charge rendue etait mesuree sur TOUT le parc local (`len(inv.busy) + len(inv.unknown)`), alors que la liberation rend la COHORTE a l'offre hebergee. Or le parc porte en permanence des jobs STATIQUES (97 lignes `runs-on` en `[self-hosted, coursia-ephemeral, coursia-linux]`) qui ne reviendront jamais sur l'heberge : avec `charge <= 2`, la liberation n'avait donc JAMAIS lieu, et la variable restait a `true` a perpetuite -- l'inverse de la consigne de la flotte (heberge d'abord). Ce que fait cette PR : - la liste de la cohorte est DERIVEE du depot a chaque passage (`derive_cohort_workflows`) : un workflow y entre si son contenu lit `vars.CI_OVERFLOW_LOCAL`, lignes de commentaire exclues. Jamais recopiee ; un workflow qui rejoint la forme hybride y entre sans toucher cet organe. - la charge rendue est celle des jobs de la cohorte (en file + en cours), lus par le meme `fetch_starvation` que l'affamescence -- le filtre par chemin ne coute donc aucun appel de plus. `JobRow` porte desormais le chemin du workflow (`run.path`). - la marge hebergee = plafond nomme (`HOSTED_CONCURRENCY_CAP`, 20 -- mesure 19 le 16/10) moins les jobs heberges en file et en cours. - la liberation exige que les DEUX lectures de la charge rendue tiennent dans la marge, sur la meme seconde lecture espacee qu'avant. - fail-CLOSED conserve : liste de cohorte vide ou illisible -> UNREADABLE ; lecture tronquee, run en cours non lu, marge nulle ou negative -> blocage nomme. Le seuil absolu `--max-returned-jobs` est retire (aucun parametre mort dans le rapport) ; l'inventaire du parc reste rapporte comme information operateur, sans decider. - le `sparse-checkout` du workflow gagne `.github/workflows` : sans lui la derivation serait vide en CI et la liberation impossible a perpetuite. Tests : 56 passants (les trois cas demandes + derives + bornes + CLI de bout en bout). Preuve du correctif : le meme scenario (parc charge de jobs statiques, cohorte vide, heberge sous son plafond) rend HOLD sur le code d'avant et SWITCH_FALSE sur le nouveau. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#23041): retire la correction de date du PAT, deja livree par #23050 Collision de ma propre flotte : #23050 (PR dediee a #22946) corrigeait deja la date d'expiration du secret `ACTIONS_VARIABLES_PAT` dans les memes fichiers (workflow, doc, controleur). La garder ici livrait la meme correction deux fois, dans deux PRs ouvertes sur les memes chemins -- une collision de livraison et un conflit de fusion sur les memes lignes. Cette PR garde son sujet propre : la liberation mesure la CHARGE RENDUE DE LA COHORTE et la compare a la MARGE HEBERGEE. La correction de la date reste dans #23050, qui la porte dediee. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Docs(ci,#23041): les compteurs de prose sont supprimes, le predicat reste Le detecteur `prose-counts` refuse les mesures figees dans la prose (#9377) : le compte de fichiers de workflow et le compte de lignes `runs-on` derives de la base sont supprimes, la affirmation qui les portait reste. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#22937): la lecture locale est ciblee par chemin de workflow Le controleur de debordement lisait les runs en file du plus vieux au plus recent et ne pouvait savoir, avant de lire les jobs, si un run attendait l'heberge ou le parc local. Sous saturation, les runs anciens de la file sont presque tous des runs `ubuntu-latest` : ils consommaient seuls le budget de 120 lectures, la lecture locale se tronquait avant d'etablir quoi que ce soit, et la soupave se fermait exactement dans le regime ou elle devait s'ouvrir (mesure du coordinateur, run 40730110344 : 129 job(s) heberge(s) en file, 16 slot(s) local(aux) libre(s), lecture locale TRONQUEE -> HOLD). La lecture locale ne vise plus que les runs dont le workflow PEUT porter un job `coursia-linux` (liste derivee des fichiers de workflow a chaque passage, jamais recopiee) : un run hors perimetre est ecarte SANS lecture et son exclusion ne retire rien a la mesure. Un `path` absent ou une expression de `runs-on` non auditee sont traites comme concerned (fail- safe) ; seule la LISTE vide ou illisible fait echouer la derivation, et alors le verdict est UNREADABLE -- jamais un enclenchement par defaut. La connaissance « quel workflow atterrit sur quel runner » reste dans l'organe qui la possede (`check_self_hosted_runner_policy.local_capable_ workflows`) : le controleur ne reecrit pas un second parseur de YAML. Trois tests nommes par le coordinateur, et chacun prouve par rejeu : file hebergee saturee de 250 runs anciens + 16 slots libres + budget 120 -> le controleur s'enclenche ; un job statiquement local en file depuis plus de 15 min -> il tient ; liste des workflows illisible ou vide -> UNREADABLE. Mesure A/B sur la file reelle (2026-10-28, api.github.com) : avant le correctif la lecture locale est TRONQUEE (96 run(s) non lu(s), budget 120 atteint) ; apres, elle est complete (0 job affame, 21 slot(s) libre(s)). Suite : 174 passed (check_self_hosted_runner_policy 74, check_runner_ starvation 39, ci_overflow_controller 61). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#23148): PyYAML installe dans le job du controleur -- la derive locale n'est plus refusee en permanence Revue coordinateur (myia-ai-01) du 2026-10-29 sur #23148, point bloquant : la dependance PyYAML n'existait pas dans le job qui execute le controleur. Mesure A/B sur ce siege (meme organe, meme tete, deux environments) : - avec PyYAML : "local_capable": {"workflows": 127, "derivation": ""} -> verdict HOLD (mesure) - sans PyYAML : "local_capable": {"workflows": 0, "derivation": "PyYAML est indisponible"} -> verdict UNREADABLE, raison "parc local non mesure" Le second etat etait celui du job en production : `local_paths_reason` non vide a chaque passage -> `collect_local` rend `complete=False` -> UNREADABLE -> aucun enclenchement, jamais. La soupape restait fermee pour une autre raison que celle qu'elle surveille. Correction : etape "Install PyYAML if absent" (import-test en dry-run, fallback pip install silencieux) entre "Set up Python" et "Verdict et bascule", forme identique a celle du workflow frere `always-on-metadata-guards.yml`. La legende d'en-tete "aucune dependance ajoutee" etait fausse : elle est remplacee par le constat. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
…ns mergees sur main Mesure du 2026-10-30 par les fonctions du garde (une exception est vivante ssi elle couvre encore au moins un job PR nu) : 8 vivantes sur 21. Retirees des deux structures (dict + baseline), selon la procedure documentee au-dessus des constantes : - 4 converties par #23016 (merge 2026-10-29) : always-on-metadata-guards, enrich-quality-gate, notebook-validation, quarto-render-list-freshness ; - 8 converties par #23058 famille (b) (merge 2026-10-30) : dotnet-AgentSafetyAnalyzer, dotnet-ArgumentAnalysis, dotnet-OrphanApps, dotnet-Shared.Tests, gametheory-tests, genai-helpers-tests, ict-tests, scripts-tests ; - lean-planning.yml, supprime par #23467. Une exception perimee desarme le cliquet pour son workflow : un job nu reintroduit y passerait inapercu. Les motifs des 8 vivantes sont reecrits sur ce qui est mesure dans les fichiers au jour de la fusion (offre GitHub d'abord #17397, OOM #15202, FORK_REACHABLE #14201, conversion #23059 en vol). Controles : pytest du cliquet 88 passed ; garde --check rc=0 (8 exceptions vivantes sur 8 en baseline) ; garde --check --check-timeouts rc=0 (62 jobs sans borne, dont 62 en baseline #22954, 0 nouveaux). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…ntu-latest nu (#23063) * Fix(ci,#23053): cliquet de politique sur les jobs PR restes sur ubuntu-latest nu Volet (a) de #23037/#23053 : le cliquet qui fait que le prochain workflow ajoute ne recree pas la file. Le defaut mesure dans l'issue : 56 runs en file et 30 en cours, 12 slots locaux libres, et un echantillon de 12 runs en file dont TOUS les jobs en attente demandent `ubuntu-latest`. Le debordement est arme, mais seuls les 29 workflows qui lisent `CI_OVERFLOW_LOCAL` en profitent. Le garde refuse maintenant tout job d'un workflow declenche par `pull_request` laisse sur un `ubuntu-latest` **nu**, sauf si le workflow est nomme dans `HOSTED_PR_JOB_EXCEPTIONS` avec son motif. La liste est un sous-ensemble d'une baseline separee et litterale : **ajouter** une exception sans etendre la baseline est lui-meme une violation (`HOSTED_PR_JOB_EXCEPTION_ADDED`), donc la liste ne peut que retrecir. Une exception sans motif est refusee (`HOSTED_PR_JOB_EXCEPTION_NO_MOTIVE`). Cinq jobs convertis en forme hybride (runs-on byte-identique a #23016) : `assert-memory-budget`, `docs-reference-guard`, `dryrun-sizing-control`, `pedagogy-density-advisory`, `twin-attestation-name-guard`. Bornes de duree posees ou conservees, chacune avec la mesure qui la fonde. Le recensement de l'issue etait **partiellement perime** sur `main` : `bash-syntax-advisory`, `markdown-claims-output-advisory`, `source-output-ratchet` et `translation-guard` sont deja en self-hosted statique, pas en `ubuntu-latest` nu ; les convertir en hybride les ferait basculer sur l'heberge par defaut, ce qui serait une regression de routage. Ils ne sont donc pas touches, et la table de l'issue le dit. Deux workflows restent heberges avec un motif verifie empiriquement : `local-path-waiver-guard` (`issue_comment`) et `perimeter-review-guard` (`pull_request_review`) sont des declencheurs FORK_REACHABLE, et le garde refuse la jambe self-hosted sur ces declencheurs. Verifications : garde rc=0 (`22 workflow(s) excepte(s) sur 22`) ; `test_check_self_hosted_runner_policy.py` 77 passed (8 nouveaux) ; suite complete 1 failed / 9930 passed -- l'echec est `jupyter-nbconvert` absent de la machine, hors diff. Signale hors perimetre : `bash-syntax-advisory` (3 jobs) et `pedagogy-density-advisory` n'ont aucun `timeout-minutes` sur des jobs locaux -- un job suspendu y tient un slot 360 min. A traiter separement. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#23053): sparse-checkout en mode non-cone pour la jambe locale Les trois workflows convertis en forme hybride de debordement (dryrun-sizing-control, local-path-waiver-guard, twin-attestation-name-guard) sortent en rouge sur le runner local `coursia-linux` : le checkout echoue avant meme la premiere etape. Cause mesuree : le runner local embarque git 2.43.0, dont le mode par defaut (cone) refuse un chemin de FICHIER dans `git sparse-checkout set` ("fatal: '...' is not a directory", rc=128). Le runner GitHub heberge, lui, embarque git 2.56.0, qui accepte la forme -- d'ou le meme workflow vert sur `ubuntu-latest` et rouge en local. Reproduction locale (WSL, git 2.43.0, le meme binaire que le runner) : cone + chemin de fichier -> rc=128 --no-cone + chemin de fichier -> rc=0, les deux fichiers materialises repertoires (cone) -> rc=0, mais materialise 30 Mo de scripts/ Correction : `sparse-checkout-cone-mode: false` sur les trois checkouts -- la forme deja employee par `stale-guard-red-sweep.yml` (job vert sur un runner self-hosted, run 40485701553), `adjacency-stale-sweep.yml` et `candidate-delivered-advisory.yml`. Les chemins restent SANS slash initial : la forme `/chemin` que suggere l'avertissement de git rend un checkout vide (mesure du repo, stale-guard-red-sweep.yml). Portee : 3 lignes `sparse-checkout-cone-mode` + commentaires. Aucun changement de declencheur, de `runs-on`, de borne de duree ni de script. Co-Authored-By: Claude-Code <noreply@anthropic.com> * Fix(ci,#23053): creation-provenance-advisory passe en forme hybride + allowlist `creation-provenance-advisory.yml` est arrive sur `main` par #23185 (meme lane), donc **apres** l'ecriture du cliquet : son job etait nu sur `ubuntu-latest`, ce que le cliquet signale (`HOSTED_PR_JOB_UNEXCEPTED`). Correction, dans le meme geste que le cliquet : - expression hybride auditee #22461 : PR meme-depot ET `vars.CI_OVERFLOW_LOCAL == 'true'` -> jambe Linux locale ; sinon `ubuntu-latest` ; - `if:` anti-fork conserve a l'identique ; - `timeout-minutes: 10` ; - entree dans `SELF_HOSTED_WORKFLOW_ALLOWLIST`, avec son motif dans le journal de l'allowlist. **Sans effet de comportement au 29/10** : la variable de depot vaut `false`, le routage resout donc toujours vers `ubuntu-latest`. La conversion retire la violation du cliquet sans changer ou le job s'execute. Bascule : `gh variable set CI_OVERFLOW_LOCAL --body true|false --repo jsboige/CoursIA`. Retour arriere = variable a `false` ou supprimee, sans toucher ce fichier. Organe : `check_self_hosted_runner_policy.py` -> OK (169 workflows, 214 jobs, 139 self-hosted) ; 170 tests verts, 1 skip. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#23053): retraite des 13 exceptions perimees par les conversions mergees sur main Mesure du 2026-10-30 par les fonctions du garde (une exception est vivante ssi elle couvre encore au moins un job PR nu) : 8 vivantes sur 21. Retirees des deux structures (dict + baseline), selon la procedure documentee au-dessus des constantes : - 4 converties par #23016 (merge 2026-10-29) : always-on-metadata-guards, enrich-quality-gate, notebook-validation, quarto-render-list-freshness ; - 8 converties par #23058 famille (b) (merge 2026-10-30) : dotnet-AgentSafetyAnalyzer, dotnet-ArgumentAnalysis, dotnet-OrphanApps, dotnet-Shared.Tests, gametheory-tests, genai-helpers-tests, ict-tests, scripts-tests ; - lean-planning.yml, supprime par #23467. Une exception perimee desarme le cliquet pour son workflow : un job nu reintroduit y passerait inapercu. Les motifs des 8 vivantes sont reecrits sur ce qui est mesure dans les fichiers au jour de la fusion (offre GitHub d'abord #17397, OOM #15202, FORK_REACHABLE #14201, conversion #23059 en vol). Controles : pytest du cliquet 88 passed ; garde --check rc=0 (8 exceptions vivantes sur 8 en baseline) ; garde --check --check-timeouts rc=0 (62 jobs sans borne, dont 62 en baseline #22954, 0 nouveaux). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#23053): cycle-time-guard converti en forme hybride de debordement Le job `cycle_time` de `cycle-time-guard.yml` etait nu sur `ubuntu-latest` dans un workflow declenche par `pull_request` : violation `HOSTED_PR_JOB_UNEXCEPTED` du cliquet de politique (#23053), qui rendait rouge `test_real_repository_hosted_pr_jobs_are_all_excepted`, `test_current_repository_self_hosted_jobs_satisfy_isolation_policy` et `test_22954_repository_passes_the_ratchet_with_zero_new_jobs`. Conversion en forme hybride de debordement, comme `creation-provenance-advisory.yml` dans cette meme PR : meme expression `runs-on` auditee (PR meme-depot ET `vars.CI_OVERFLOW_LOCAL == 'true'` -> jambe Linux locale, sinon `ubuntu-latest`), `if:` anti-fork conserve, `timeout-minutes: 10` conserve, et inscription de l'allowlist dans le meme fichier que le cliquet. La variable vaut `false` au 2026-10-31 : le routage est donc inchange (100 % heberge) -- la conversion retire la violation sans changer ou le job s'execute. Rollback = variable a `false` ou supprimee. Preuve : `python -m pytest scripts/tests/test_check_self_hosted_runner_policy.py -q` -> 88 passed ; sous-ensemble sensible aux workflows (audit des besoins locaux, controleur de debordement, starvation, fast-lane, filtres de chemins, references de steps) -> 300 passed ; `python scripts/ci/check_self_hosted_runner_policy.py --check` -> rc=0. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
…(le choix, mesure d'abord) (#23150) * Fix(ci,#22937): le controleur libere par l'AGE de la file, pas par son VIDE Sortie de la PR de forme hybride (#23016) a la demande du coordinateur : le volet controleur part dans sa propre PR, pour que les deux se merge independamment et sans qu'un fichier soit modifie par les deux. La condition de liberation passe de « file hebergee videe » (hosted.queued <= drain_jobs) a « l'offre hebergee suit » (hosted.old == 0). L'ancienne premise etait INATTEIGNABLE : le depot porte toujours des jobs heberges hors debordement (CodeQL en setup par defaut, Lean CI, les advisories de starvation), donc la variable ne pouvait plus jamais revenir a `false` une fois posee -- le controleur etait un commutateur a SENS UNIQUE, et le parc local gardait la cohorte pendant que l'offre hebergee unusedait ses propres slots. Mesure du 27/10 a 22h50Z : variable a `true` depuis 11h04Z, hosted.queued=74, hosted.old=0 (l'offre suit), 29 slots locaux libres. --drain-jobs et DEFAULT_DRAIN_JOBS sont retires : le parametre n'a plus de sens. La lecture tronquee continue de bloquer la liberation (fail-CLOSED). Le dwell reste a 90 min (il doit depasser la cadence de 60 min). Corrige aussi la date d'expiration du PAT : regenere le 27/10 a 12 mois (2027-10-27), la date ecrite sur main (2027-01-25) etait perimee. Tests : scripts/tests/test_ci_overflow_controller.py -> 35 passed (4 ajoutes). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#22937): la liberation PREDIT la charge rendue (parc local inactif, 2 lectures) Reponse a la reserve du coordinateur (28/10) sur la regle de liberation : `hosted.old == 0` lit l'offre hebergee PENDANT que la cohorte tourne en local, donc une offre allegee par construction -- a la liberation, toute la cohorte revient sur cette offre, et le dwell de 90 min empeche de revenir a `true` : on rejoue la nuit du 27/10. La liberation exige desormais que la charge RENDUE soit predite et bornee : le parc local ne porte presque rien (au plus `--max-returned-jobs` slots occupes, 2 par defaut ; l'occupation inconnue compte comme occupation, fail-CLOSED), et la lecture est CONFIRMEE par une seconde lecture de l'inventaire espacee de `--confirm-seconds` (30 s). Un HOLD nomme la condition manquante. Le dwell est desormais asymetrique : il oppose toujours la LIBERATION (c'est elle qui fait revenir la cohorte), mais il ne bloque plus la RE-ABSORPTION quand le backlog heberge est DUR (plus ancien job au-dela de `--panic-minutes`, 30 min) : la nuit du 27/10, attendre 90 min devant une starvation etablie prolongeait la panne. L'echappement ne peut pas creer d'oscillation -- il exige un backlog dur, incompatible avec la premise de liberation. La regle `hosted.old == 0` seule etait un ARTEFACT : c'est l'etat de la FILE LOCALE qui borne la charge rendue, pas celui de l'offre hebergee. Tests : scripts/tests/test_ci_overflow_controller.py -> 42 passed (7 ajoutes sur les 35 precedents). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#23041): la liberation compare la CHARGE RENDUE DE LA COHORTE a la MARGE HEBERGEE Review 5677772216 : la charge rendue etait mesuree sur TOUT le parc local (`len(inv.busy) + len(inv.unknown)`), alors que la liberation rend la COHORTE a l'offre hebergee. Or le parc porte en permanence des jobs STATIQUES (97 lignes `runs-on` en `[self-hosted, coursia-ephemeral, coursia-linux]`) qui ne reviendront jamais sur l'heberge : avec `charge <= 2`, la liberation n'avait donc JAMAIS lieu, et la variable restait a `true` a perpetuite -- l'inverse de la consigne de la flotte (heberge d'abord). Ce que fait cette PR : - la liste de la cohorte est DERIVEE du depot a chaque passage (`derive_cohort_workflows`) : un workflow y entre si son contenu lit `vars.CI_OVERFLOW_LOCAL`, lignes de commentaire exclues. Jamais recopiee ; un workflow qui rejoint la forme hybride y entre sans toucher cet organe. - la charge rendue est celle des jobs de la cohorte (en file + en cours), lus par le meme `fetch_starvation` que l'affamescence -- le filtre par chemin ne coute donc aucun appel de plus. `JobRow` porte desormais le chemin du workflow (`run.path`). - la marge hebergee = plafond nomme (`HOSTED_CONCURRENCY_CAP`, 20 -- mesure 19 le 16/10) moins les jobs heberges en file et en cours. - la liberation exige que les DEUX lectures de la charge rendue tiennent dans la marge, sur la meme seconde lecture espacee qu'avant. - fail-CLOSED conserve : liste de cohorte vide ou illisible -> UNREADABLE ; lecture tronquee, run en cours non lu, marge nulle ou negative -> blocage nomme. Le seuil absolu `--max-returned-jobs` est retire (aucun parametre mort dans le rapport) ; l'inventaire du parc reste rapporte comme information operateur, sans decider. - le `sparse-checkout` du workflow gagne `.github/workflows` : sans lui la derivation serait vide en CI et la liberation impossible a perpetuite. Tests : 56 passants (les trois cas demandes + derives + bornes + CLI de bout en bout). Preuve du correctif : le meme scenario (parc charge de jobs statiques, cohorte vide, heberge sous son plafond) rend HOLD sur le code d'avant et SWITCH_FALSE sur le nouveau. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#23041): retire la correction de date du PAT, deja livree par #23050 Collision de ma propre flotte : #23050 (PR dediee a #22946) corrigeait deja la date d'expiration du secret `ACTIONS_VARIABLES_PAT` dans les memes fichiers (workflow, doc, controleur). La garder ici livrait la meme correction deux fois, dans deux PRs ouvertes sur les memes chemins -- une collision de livraison et un conflit de fusion sur les memes lignes. Cette PR garde son sujet propre : la liberation mesure la CHARGE RENDUE DE LA COHORTE et la compare a la MARGE HEBERGEE. La correction de la date reste dans #23050, qui la porte dediee. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Docs(ci,#23041): les compteurs de prose sont supprimes, le predicat reste Le detecteur `prose-counts` refuse les mesures figees dans la prose (#9377) : le compte de fichiers de workflow et le compte de lignes `runs-on` derives de la base sont supprimes, la affirmation qui les portait reste. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#22937): la lecture locale est ciblee par chemin de workflow Le controleur de debordement lisait les runs en file du plus vieux au plus recent et ne pouvait savoir, avant de lire les jobs, si un run attendait l'heberge ou le parc local. Sous saturation, les runs anciens de la file sont presque tous des runs `ubuntu-latest` : ils consommaient seuls le budget de 120 lectures, la lecture locale se tronquait avant d'etablir quoi que ce soit, et la soupave se fermait exactement dans le regime ou elle devait s'ouvrir (mesure du coordinateur, run 40730110344 : 129 job(s) heberge(s) en file, 16 slot(s) local(aux) libre(s), lecture locale TRONQUEE -> HOLD). La lecture locale ne vise plus que les runs dont le workflow PEUT porter un job `coursia-linux` (liste derivee des fichiers de workflow a chaque passage, jamais recopiee) : un run hors perimetre est ecarte SANS lecture et son exclusion ne retire rien a la mesure. Un `path` absent ou une expression de `runs-on` non auditee sont traites comme concerned (fail- safe) ; seule la LISTE vide ou illisible fait echouer la derivation, et alors le verdict est UNREADABLE -- jamais un enclenchement par defaut. La connaissance « quel workflow atterrit sur quel runner » reste dans l'organe qui la possede (`check_self_hosted_runner_policy.local_capable_ workflows`) : le controleur ne reecrit pas un second parseur de YAML. Trois tests nommes par le coordinateur, et chacun prouve par rejeu : file hebergee saturee de 250 runs anciens + 16 slots libres + budget 120 -> le controleur s'enclenche ; un job statiquement local en file depuis plus de 15 min -> il tient ; liste des workflows illisible ou vide -> UNREADABLE. Mesure A/B sur la file reelle (2026-10-28, api.github.com) : avant le correctif la lecture locale est TRONQUEE (96 run(s) non lu(s), budget 120 atteint) ; apres, elle est complete (0 job affame, 21 slot(s) libre(s)). Suite : 174 passed (check_self_hosted_runner_policy 74, check_runner_ starvation 39, ci_overflow_controller 61). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Feat(ci,#22937): un declencheur qui tire -- cadence locale sur ai-01 Le `schedule` horaire reste en filet, mais il ne peut pas etre le seul declencheur : mesure du coordinateur, le cron '31 * * * *' n'a ete servi que 3 fois le 28/10 (00:39, 07:10, 14:19Z). L'option `workflow_run` sur la fin du PR gate est IMPOSSIBLE ici, pour deux raisons mesurees : 1. la politique d'isolation du depot REFUSE `workflow_run` sur un runner self-hosted (`check_self_hosted_runner_policy.py`, code `WORKFLOW_RUN`) ; or le controleur tourne sur le parc local -- le mettre sur l'offre hebergee le ferait faire la queue dans l'offre qu'il observe (review ai-01 du 27/10) ; 2. un declencheur sur `ubuntu-latest` ferait la queue dans l'offre SATUREE : le coordinateur a mesure des gardes de quelques secondes crees entre 15:56 et 16:21Z qui n'ont demarre qu'entre 17:03 et 17:10Z -- environ une heure de file. Le declencheur arriverait apres l'evacuation de la saturation. Reste la cadence locale sur ai-01, le siege qui heberge le parc de debordement : elle ne traverse aucune file. La tache planifiee DECLENCHE le workflow du depot -- c'est le workflow qui mesure et decide, avec les secrets du depot ; le siege est une minuterie, pas un decisionnaire. Cablage : `scripts/ci/install_overflow_trigger_task.py`, meme forme que `scripts/coordination/install_merge_ready_task.py` qui tourne deja sur ce siege. Tache `CoursIA\overflow_trigger` toutes les 15 min, depart 00:07 (phase decalee du cron :31 du depot), action `gh workflow run ci-overflow-controller.yml -f min_interval=15`, fenetre masquee, journal sous %LOCALAPPDATA%\CoursIA\overflow_trigger\. Le jeton est lu dans le trousseau du siege AU MOMENT du tir (`gh auth token --user jsboige`) : aucun jeton n'est ecrit sur disque. `--dry-run` par defaut : il imprime la commande schtasks exacte et le contenu exact des deux lanceurs sans rien ecrire (discipline UAC). L'intervalle minimal entre deux decisions est porte par l'organe lui-meme (`--min-interval`) : un passage dont le precedent est trop recent rend SKIPPED AVANT toute mesure -- il ne lit rien, ne consomme ni appel API ni slot du parc local (le controleur tourne SUR ce parc). Un ecart non etabli (listing illisible) ne bloque pas le passage : eteindre la soupape parce qu'on n'a pas su lire un ecart serait l'inverse du sens. Un declenchement manuel passe min_interval=0 : la decision de l'operateur est immediate. Suite : 182 passed (dont 5 nouveaux tests de l'installateur et 3 de la garde de cadence). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Feat(ci,#22937): le declencheur ne passe que les inputs que le depot declare `gh workflow run -f min_interval=15` echoue si la branche par defaut ne declare pas l'input. Pendant la fenetre de fusion (le workflow du depot ne declare pas encore `min_interval`), CHAQUE tir du declencheur local aurait echoue -- silencieusement, dans le journal. L'installateur lit donc le workflow SUR LE DEPOT (`contents` de la branche par defaut) et n'ajoute `-f min_interval=...` que si l'input y existe. Le declencheur traverse ainsi la fenetre de fusion, et le gain de cadence s'active apres le merge, au prochain `--install`. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#22937): le motif du declencheur absent distingue l'absence de l'input `workflow_dispatch:` sans valeur = declencheur SANS input (se cable) ; cle absente = declencheur workflow_dispatch absent (ne se cable pas). La raison affichee par le dry-run nomme desormais le bon cas. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#22937): le journal du declencheur est celui que le lanceur ecrit `log_path_for()` annoncait un fichier DATE alors que le lanceur `.cmd` ecrit un fichier de nom fixe -- la commande `--run` affichait donc un chemin de journal inexistant. Le lanceur est le seul a fixer le nom (la date du systeme depend de la locale) : l'organe s'aligne sur lui. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#23150): la cadence locale passe a 60 min -- le plafond d'appels la fixe, pas une preference Suite a la reserve Hermes (point sur la tension quantitative interne) : - une mesure coute 313 appels `gh` (mesure du 2026-10-27) et le jeton du depot (`github.token`) plafonne a 1 000 appels/h pour le repo, partage avec les autres jobs ; - le cron horaire du depot consomme deja 313 appels/h ; - a 15 min, la minuterie locale seule portait ~1 250 appels/h : au-dessus du plafond, le declencheur affamait l'organe qu'il pilote, et son etat rendu devenait illisible (UNREADABLE -- sur mais muet). La minuterie locale passe donc a 60 min (un passage par heure), soit ~630 appels/h avec le cron du depot : 63 % du plafond. Ce qu'elle apporte n'est pas de la frequence mais de la FIABILITE -- la livraison des crons du depot est mesuree a 22 % des tics servis (mediane 289 min de retard). La garde `min_interval` passe de 15 a 30 min : elle doit rester STRICTEMENT inferieure a la cadence, parce que l'ecart se lit sur le dernier passage (run confondu) et que le cron du depot mesure a :31, soit 36 min avant le tic local de :07 -- une garde egale a la cadence ecarterait ce tic. Le cron du depot garde sa mesure sans garde (il reste le filet horaire si la minuterie locale meurt). La coherence est desormais un contrat executable : `test_la_cadence_par_defaut_respecte_le_plafond_d_appels_de_gh`. Tache planifiee du siege reinstallee : 60 min, prochain tic 08:07. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * Fix(ci,#23150): l'ecart au dernier passage est toujours mesure et rapporte Suite au deuxieme point de la revue Hermes (degradation silencieuse du declencheur local) : l'ecart au dernier passage se lisait par un appel `gh` uniquement quand une garde etait posee -- donc jamais sur le cron horaire du depot ni sur un passage manuel. Il est desormais mesure AVANT la garde et reporte dans TOUS les cas ; le resume affiche toujours `dernier passage : N min`. C'est l'instrument qui rend visible la mort du declencheur local (un ecart qui grandit) au lieu de la laisser silencieuse, et son cout est d'UN appel dans un passage qui en compte 313. La suite de tests est rendue hermetique : un fixture automatique simule l'ecart, pour qu'aucun test n'atteigne le reseau (#21994). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
… Quarto et slides (#23059) Les jobs declenches par `pull_request` des 8 workflows de la famille (c) prennent la forme hybride de debordement CI (#22937/#22944/#23016) : garde meme-depot && vars.CI_OVERFLOW_LOCAL == 'true' -> jambe Linux locale, sinon ubuntu-latest. Par defaut (variable absente ou fausse, forks, push/schedule/dispatch), chaque job resout sur ubuntu-latest : l'etat du 27/10 est conserve. Jobs convertis (9) : lean-ci-matrix `changes` ; lean-conway, lean-knot et lean-planning `target-coverage` ; lean-i18n-drift `i18n-drift` ; quarto-pages-deploy `validate-pr` ; slides-build-advisory et slides-composition-pr-relay. Bornes de duree : deux jobs recevaient la forme hybride sans borne (lean-conway `target-coverage` -> 360 min, plafond assume et commente ; lean-i18n-drift `i18n-drift` -> 5 min, plancher). Les six autres avaient deja une borne, conservee telle quelle comme demande. Exceptions nommees, laissees hebergees : quarto-pages-deploy `build` (OOM mesure sur le pool, #15202 ; garde push-only de toute facon) et `deploy` (push-only, deploy-pages + OIDC/PAGES). Les jobs `uses:` reutilisables ne portent pas de runs-on et restent hors de la forme. Allowlist : lean-ci-matrix.yml et lean-planning.yml ajoutes a SELF_HOSTED_WORKFLOW_ALLOWLIST, dans un bloc delimite en fin d'ensemble ; les six autres workflows de la famille y etaient deja. Verifications : parse YAML 8/8 ; check_self_hosted_runner_policy.py --check rc=0 (168 workflows, 135 jobs self-hosted) ; 194 tests des organes voisins verts ; check_workflow_label_paths, check_concurrency_conj, check_unique_check_run_names, check_lake_matrix_paths et check_axiom_gate_coverage rc=0. Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> Co-authored-by: myia-ai-01 <myia.ai.01.myia@gmail.com>
Grain: DEEP/guard — lane myia-ai-01:CoursIA-2 — prev: LIGHT/guard #22944
Objet
Vague 2 du debordement CI (#22937, volet 1). La phase A (#22921) a pose la forme hybride sur quelques workflows de garde ; cette PR l'etend a 5 workflows / 6 jobs -- le perimetre accorde (« 5 workflows par volume decroissant, puis le reste en seconde PR »).
twin-parity.ymln'est pas dans cette PR : il est porte par une PR dediee (#22944) deja mergee. Les deux PRs ne peuvent pas toucher le meme workflow.Ce que cette PR ne contient plus. A la demande du coordinateur (review
5676664097du 28/10), le volet controleur en est sorti, et les trois lignesif:ajoutees ont ete retirees. Le volet controleur n'est pas perdu : il part dans une PR dediee (#23041), pour que les deux se merge independamment et sans qu'un fichier soit modifie par les deux. Le diff de cette PR porte desormais uniquement la forme hybride (5 workflows, 6 jobs) et l'allowlist de l'instrument de politique self-hosted.Ce que ce push change par rapport a la version revue
scripts/ci/ci_overflow_controller.pyetscripts/tests/test_ci_overflow_controller.py(revenus a l'identique deorigin/main).github/workflows/ci-overflow-controller.ymletdocs/coordination/ci-overflow-controller.mdif:ajoutees (enrich-quality-gate.yml,quarto-render-list-freshness.yml)runs-on(github.event.pull_request.head.repo.full_name == github.repository) ; une ligneif:de plus etait redondanteif:ajoute, que la review n'avait pas comptevalidate-staticdenotebook-execution-required.yml: la cleif:y existait deja. Il est retire, lui aussi.Mesure du diff courant : 6 fichiers, +91/-15. Aucune ligne
if:ni ajoutee ni supprimee. Les 6 workflows concernes se parsent sans cle YAML dupliquee (controle par un parseur a detection de doublon, sur les 6 fichiers de workflow).Le perimetre compte de la meme facon par tous les instruments : 5 workflows portent la forme hybride (
always-on-metadata-guards,enrich-quality-gate,notebook-execution-required,notebook-validation,quarto-render-list-freshness), soit 6 jobs au total (notebook-execution-required.ymlen porte 2).La forme hybride
PR meme-depot et
vars.CI_OVERFLOW_LOCAL == 'true'-> jambe Linux locale ; tout le reste (variable absente ou fausse, forks,push/schedule/workflow_dispatch) ->ubuntu-latest, c'est-a-dire l'etat actuel du 27/10. C'est un XOR : un job ne change pas de cohorte, il change de camp.La garde anti-fork est dans l'expression elle-meme (
head.repo.full_name == github.repository) : c'est pourquoi aucune ligneif:supplementaire n'est necessaire, et c'est pourquoi celles qui avaient ete ajoutees ont ete retirees.Pourquoi DEEP : la partie non mecanique n'est pas l'application de la forme a un workflow de plus, c'est l'audit de portabilite job par job (11 workflows et 16 jobs lus a la source, avec la cause reelle de chaque exclusion : service GitHub, politique de declencheur, role de chien de garde, poids, binaire absent). Appliquer la forme au workflow suivant serait mecanique ; savoir lequel est portable ne l'est pas.
Perimetre : 5 workflows, 6 jobs
notebook-execution-required.ymldetect-changes,validate-staticalways-on-metadata-guards.ymlalways-on-metadata-guardsquarto-render-list-freshness.ymlchecknotebook-validation.ymlvalidate-notebooksenrich-quality-gate.ymlenrich-qualityVolontairement laisse statique :
golden-set-execute(execution lourde du golden set, pas un job de file) et le job de render quarto (le binairequarton'existe pas dans l'image locale).uses:detect-changescheckout@v4(fetch-depth: 0,filter: blob:none)git,grepvalidate-staticsetup-python@v5,github-script@v7nbformatgithub-scriptalways-on-metadata-guardssetup-python@v5python -c "import yaml" 2>/dev/null || pip install --quiet pyyamlpyyaml(repli silencieux)check(quarto)checkout@v4python3 scripts/regen_quarto_render.py --checkgit,sed,jqvalidate-notebookssetup-python@v5nbformatfindenrich-qualitycheckout@v4,setup-python@v5gitImage locale
coursia-linux:python3,python3-pip,python-is-python3,python3-yaml,git,jq,curl,wget,zstd,node24dans/opt/runner/externals/. Pas dequarto, pas dedotnet, pas de Lean.Les scripts des jobs
check(quarto) etenrich-qualityont ete relus a la source : aucune dependance hors stdlib. Les scripts dealways-on-metadata-guardsutilisentpyyaml, presente dans l'image, avec le replipip installdeja en place. Les deux jobs qui utilisentnbformatl'installent explicitement.Aucun de ces workflows n'utilise
github/codeql-action,container:,services:,dotnetniplaywright: rien qui manquerait au toolcache local.Le chiffre de deversement
Le coordinateur attendait le chiffre avant de trancher. Il est mesure sur l'activite reelle du depot, pas sur une file ponctuelle.
Fenetre 21h30Z-22h05Z (35 min), 300 runs du depot (
GET /actions/runs, 3 pages de 100, echantillon le plus recent) :notebook-execution-required.ymlalways-on-metadata-guards.ymlquarto-render-list-freshness.ymlenrich-quality-gate.ymlnotebook-validation.ymltwin-parity.ymlCes 5 workflows portent un dixieme de l'activite CI du depot : 41 jobs sur 35 minutes d'activite. C'est le deversement : quand
CI_OVERFLOW_LOCALest atrue, ces 41 jobs partent sur le parc local au lieu de l'offre hebergee.L'echantillon est un instantane court (35 min) : il dit la part de la charge, pas un debit horaire garanti. Le debit absolu depend de l'activite du depot, qui est elle-meme concentree sur les periodes de push.
Ce que la file hebergee ajoutait (mesure du 27/10 a 22h40Z) : 81 jobs en file, dont 40 pour les 6 jobs du perimetre alors mesure (5 workflows). A 23h50Z, la file hebergee etait vide (0 job en attente, 29 slots locaux libres) : le parc local avait absorbe la charge.
Les exclusions, avec leur motif
codeql.ymlruns-ondu fichier n'est pas honoreelean-ci-matrix.ymlcoursia-lean)local-path-waiver-guard.ymlissue_comment— FORK_REACHABLE (default-deny de l'instrument)perimeter-review-guard.ymlpull_request_review— FORK_REACHABLEict-tests.ymlpyphi/numpy/scipy, 8,1 min medianes — le debordement absorbe d'abord les jobs courtsgolden-set-execute(job)quarton'existe pas dans l'image localeCe que l'audit des outils a permis de trancher
L'inventaire ci-dessus a ete produit par la lecture des 11 workflows candidats et des scripts qu'ils appellent (11 workflows, 16 jobs) : 10 fichiers / 14 jobs sont
PORTABLE_LOCAL, 1 fichier / 2 jobs sontDEPEND_HEBERGE. Les deux seulsDEPEND_HEBERGEsontquarto-pages-deploy.yml::build(actions/upload-pages-artifact@v3, service Pages) et::deploy(actions/deploy-pages@v4+ OIDCid-token: write) : ce sont des jobs de service GitHub, pas des jobs de file.Cela permet de classer chaque exclusion par sa cause reelle :
codeql.ymllean-ci-matrix.ymllocal-path-waiver-guard.yml::guardissue_comment= fork-reachable)PORTABLE_LOCAL(stdlib)perimeter-review-guard.yml::perimeterpull_request_review= fork-reachable)PORTABLE_LOCAL(stdlib)ict-tests.yml::ict-testspyphi/numpy/scipy, 8,1 min medianes)PORTABLE_LOCAL(tout est pip-installable)quarto-pages-deploy.yml::build/::deployDEPEND_HEBERGE(upload-pages-artifact, deploy-pages + OIDC)golden-set-executePORTABLE_LOCAL(lockfile)quartoabsent de l'image locale)Candidats identifies pour la vague 3
L'audit a debouche sur un candidat portable que cette vague n'inclut pas :
quarto-pages-deploy.yml::validate-pr(PORTABLE_LOCAL— Quarto installe par tarball curl sans sudo ; scripts stdlib ; pas d'API GitHub). Il n'est pas dans cette PR pour que le perimetre reste mesure et revu d'un bloc ; il est le candidat naturel de la vague 3.Le declencheur est un garde-fou de securite, pas une preference :
SAFE_SELF_HOSTED_TRIGGERSest default-deny (pull_request,push,schedule,workflow_dispatchuniquement). Un job declenche par un evenement d'un tiers n'est JAMAI deplace en local.Retour arriere
gh variable set CI_OVERFLOW_LOCAL --body false --repo jsboige/CoursIA: routage 100 % heberge, sans toucher un fichier. Les jobs deja en file ne bougent pas (runs-onse resout a la creation du job).Controles
scripts/ci/check_self_hosted_runner_policy.py --check:workflows=168 jobs=213 self_hosted=135, rc=0. Mesure de reference surorigin/main:workflows=168 jobs=213 self_hosted=129-> cette PR ajoute exactement 6 jobs a la liste self-hosted, et n'ajoute ni ne supprime aucun job.scripts/tests/test_ci_overflow_controller.py: 35 passed sur le volet controleur, livre dans #23041 — cette PR ne touche plus ce fichier.See #22937 (volet 1 de 3 : la vague 2 ne solde pas l'EPIC)
Volets restants de #22937 (hors cette PR)
Hors perimetre
La forme hybride est une bascule exclusive : pendant que la variable est a
true, l'offre hebergee ne recoit aucun de ces jobs. C'est voulu (c'est le debordement), mais cela veut dire que l'offre hebergee n'est pas un reservoir de secours pour ces 6 jobs : si le parc local tombe, ces jobs attendent la fin de la bascule. La mesure du 27/10 (31 runners en ligne, 29 libres) ne dit rien d'un incident du parc. Le pilotage d'un incident reste hors perimetre de cette PR.🤖 Generated with Claude Code